package com.grouuu.core 
{
	import com.grouuu.event.TimerEvent;
	import com.grouuu.factory.SpriteFactory;
	import com.grouuu.keyboard.IKey;
	import com.grouuu.keyboard.KeyListener;
	import com.grouuu.Map;
	import com.grouuu.mouse.IMouse;
	import com.grouuu.mouse.Mouse;
	import com.grouuu.tile.TileGraphic;
	import com.grouuu.update.ITimer;
	import com.grouuu.update.Timer;
	import flash.display.BitmapData;
	import flash.display.Shape;
	import flash.geom.Point;
	import flash.ui.Keyboard;
	import starling.core.Starling;
	import starling.display.BlendMode;
	import starling.display.Image;
	import starling.display.Quad;
	import starling.display.QuadBatch;
	import starling.display.Sprite;
	import starling.events.Event;
	import starling.events.KeyboardEvent;
	import starling.events.Touch;
	import starling.events.TouchEvent;
	import starling.events.TouchPhase;
	
	/**
	 * ...
	 * @author Grouuu
	 */
	public class Scene extends Sprite implements ITimer, IKey, IMouse
	{
		static public const TILE_WIDTH:int = 16;
		static public const TILE_HEIGHT:int = 16;
		
		static protected const NB_COL:int = 20;
		static protected const NB_ROW:int = 30;
		static protected const NB_TILE_MARGIN:int = 2;
		static protected const MOVE_SPEED:Number = 5;
		
		static protected const ORIGIN_X:int = 0;
		static protected const ORIGIN_Y:int = 0;
		static protected const STAGE_W:int = 320;
		static protected const STAGE_H:int = 480;
		static protected const IS_TEST:Boolean = false;
		
		protected var factorySprite:SpriteFactory;
		protected var timer:Timer;
		protected var keyboard:KeyListener;
		protected var mouse:Mouse;
		
		// TODO : use workers (multi-thread)
		
		public function Scene() 
		{
			addEventListener(Event.ADDED_TO_STAGE, onAdded);
		}
		
		protected function onAdded(event:Event):void
		{
			init();
		}
		
		protected function init():void
		{
			Starling.current.showStats = true;
			
			factorySprite = new SpriteFactory();
			timer = new Timer();
			keyboard = new KeyListener();
			mouse = new Mouse();
			
			quadTiles = new QuadBatch();
			
			timer.addListener(this);
			keyboard.addListener(this);
			mouse.addListener(this);
			
			timer.start();
			
			initMap();
			initHero();
			
			var border:Shape = new Shape();
			border.graphics.lineStyle(2, 0xFF0000);
			border.graphics.drawRect(ORIGIN_X, ORIGIN_Y, STAGE_W, STAGE_H);
			
			Starling.current.nativeOverlay.addChild(border);
		}
		
		// INIT ///////////////////////////////////////////////////////////////////////////////////
		
		protected var map:Array;
		protected var quadTiles:QuadBatch;
		protected var renderList:Vector.<TileGraphic>;
		protected var originTileX:int;
		protected var originTileY:int;
		protected var limitMinX:int;
		protected var limitMaxX:int;
		protected var limitMinY:int;
		protected var limitMaxY:int;
		protected var edit:Boolean;
		protected var centerPoint:Point;
		
		protected function initMap():void
		{
			map = Map.MAP;
			
			originTileX = Scene.NB_TILE_MARGIN;
			originTileY = Scene.NB_TILE_MARGIN;
			
			limitMinX = -((map[0].length - Scene.NB_COL) * Scene.TILE_WIDTH) + Scene.NB_TILE_MARGIN * Scene.TILE_WIDTH;
			limitMaxX = -Scene.NB_TILE_MARGIN * Scene.TILE_WIDTH;
			
			limitMinY = -((map.length - Scene.NB_ROW) * Scene.TILE_HEIGHT) + Scene.NB_TILE_MARGIN * Scene.TILE_HEIGHT;
			limitMaxY = -Scene.NB_TILE_MARGIN * Scene.TILE_HEIGHT;
			
			limitMinX += Scene.ORIGIN_X;
			limitMaxX += Scene.ORIGIN_X;
			limitMinY += Scene.ORIGIN_Y;
			limitMaxY += Scene.ORIGIN_Y;
			
			centerPoint = new Point(Starling.current.stage.stageWidth / 2, Starling.current.stage.stageHeight / 2);
			
			renderList = new Vector.<TileGraphic>((Scene.NB_ROW + Scene.NB_TILE_MARGIN * 2) * (Scene.NB_COL + Scene.NB_TILE_MARGIN * 2));
			
			quadTiles = new QuadBatch();
			
			quadTiles.blendMode = BlendMode.NONE;
			//quadTiles.touchable = false;
			
			quadTiles.x = limitMaxX;
			quadTiles.y = limitMaxY;
			
			addChild(quadTiles);
			
			edit = false;
			
			setRenderTileOrigin();
			
			parseTile(initRenderList);
		}
		
		protected var hero:Quad;
		
		protected function initHero():void
		{
			hero = new Quad(16, 16, 0xFF0000);
			
			hero.pivotX = hero.width >> 1;
			hero.pivotY = hero.height >> 1;
			
			hero.x = Starling.current.stage.stageWidth / 2;
			hero.y = Starling.current.stage.stageHeight / 2;
			
			addChild(hero);
		}
		
		// TILES //////////////////////////////////////////////////////////////////////////////////
		
		protected var indexRender:int;
		protected var idTile:int;
		protected var tileGraphic:TileGraphic;
		protected var img:Image;
		protected var posX:int;
		protected var posY:int;
		protected var renderTileX:int;
		protected var renderTileY:int;
		protected var renderTileMaxX:int;
		protected var renderTileMaxY:int;
		
		protected function parseTile(action:Function):void
		{
			for (var row:int = renderTileY; row < renderTileMaxY; row++)
			{
				for (var col:int = renderTileX; col < renderTileMaxX; col++)
				{
					indexRender = getIndexRender(row - renderTileY, col - renderTileX);
					
					action(indexRender, row, col);
				}
			}
		}
		
		protected function initRenderList(indexRender:int, row:int, col:int):void
		{
			idTile = map[row][col];
			
			if (!IS_TEST)
				tileGraphic = factorySprite.getTileGraphic(idTile);
			else
				tileGraphic = factorySprite.getTileGraphicTest(idTile, row, col);
			
			renderList[indexRender] = tileGraphic;
		}
		
		protected function updateTiles(indexRender:int, row:int, col:int):void
		{
			if (!edit)
			{
				tileGraphic = renderList[indexRender];
			}
			else
			{
				idTile = map[row][col];
				
				if (!IS_TEST)
					tileGraphic = factorySprite.getTileGraphic(idTile);
				else
					tileGraphic = factorySprite.getTileGraphicTest(idTile, row, col);
				
				factorySprite.deleteTileGraphic(renderList[indexRender]);
				
				renderList[indexRender] = tileGraphic;
			}
			
			posX = Scene.TILE_WIDTH * col;
			posY = Scene.TILE_HEIGHT * row;
			
			img = tileGraphic.image;
			
			img.x = posX;
			img.y = posY;
			
			if (!tileGraphic.isPassable)
			{
				img.rotation = Math.PI;
				img.x += img.width;
				img.y += img.height;
			}
			
			quadTiles.addImage(img);
		}
		
		// UTILS //////////////////////////////////////////////////////////////////////////////////
		
		protected function getIndexRender(row:int, col:int):int
		{
			return (row * (Scene.NB_COL + Scene.NB_TILE_MARGIN * 2)) + col;
		}
		
		protected function getRenderTileX(col:int):int
		{
			return col - Scene.NB_TILE_MARGIN;
		}
		
		protected function getRenderTileY(row:int):int
		{
			return row - Scene.NB_TILE_MARGIN;
		}
		
		protected function setRenderTileOrigin():void
		{
			renderTileX = getRenderTileX(originTileX);
			renderTileY = getRenderTileY(originTileY);
			
			if (renderTileX < 0)
				renderTileX = 0;
			
			if (renderTileX > map[0].length - Scene.NB_COL - Scene.NB_TILE_MARGIN * 2)
				renderTileX = map[0].length - Scene.NB_COL - Scene.NB_TILE_MARGIN * 2;
			
			if (renderTileY < 0)
				renderTileY = 0;
			
			if (renderTileY > map.length - Scene.NB_ROW - Scene.NB_TILE_MARGIN * 2)
				renderTileY = map.length - Scene.NB_ROW - Scene.NB_TILE_MARGIN * 2;
			
			renderTileMaxX = renderTileX + Scene.NB_COL + Scene.NB_TILE_MARGIN * 2;
			renderTileMaxY = renderTileY + Scene.NB_ROW + Scene.NB_TILE_MARGIN * 2;
		}
		
		// MOVE ///////////////////////////////////////////////////////////////////////////////////
		
		protected var nbStep:int;
		protected var stepMoveX:Number;
		protected var stepMoveY:Number;
		
		protected function setMove(globalPoint:Point):void
		{
			var localPoint:Point = quadTiles.globalToLocal(globalPoint);
			var heroPoint:Point = quadTiles.globalToLocal(new Point(hero.x, hero.y));
			
			var distance:Number = Math.sqrt((heroPoint.x - localPoint.x) * (heroPoint.x - localPoint.x) + (heroPoint.y - localPoint.y) * (heroPoint.y - localPoint.y));
			
			nbStep = distance / Scene.MOVE_SPEED;
			
			stepMoveX = (localPoint.x - heroPoint.x) / nbStep;
			stepMoveY = (localPoint.y - heroPoint.y) / nbStep;
			
			// TODO : le stepMoveX/Y ne doit pas être supérieur au Scene.TILE_WIDTH/HEIGHT
		}
		
		protected var moveX:Number;
		protected var moveY:Number;
		
		protected function move():void
		{
			moveX = stepMoveX;
			moveY = stepMoveY;
			
			if (!checkImpassable())
			{
				nbStep = 0;
				return;
			}
			
			moveMap();
			moveHero();
			updateOriginTiles();
			
			nbStep--;
		}
		
		protected function checkImpassable():Boolean
		{
			var boundHeroX:Number = (moveX >= 0) ? hero.width / 2 : -hero.width / 2;
			var boundHeroY:Number = (moveY >= 0) ? hero.height / 2 : -hero.height / 2;
			
			var mapPos:Point = quadTiles.globalToLocal(new Point(hero.x + moveX + boundHeroX, hero.y + moveY + boundHeroY));
			
			var idTileX:int = mapPos.x / Scene.TILE_WIDTH;
			var idTileY:int = mapPos.y / Scene.TILE_HEIGHT;
			
			idTileX -= getRenderTileX(originTileX);
			idTileY -= getRenderTileY(originTileY);
			
			indexRender = getIndexRender(idTileY, idTileX);
			
			tileGraphic = renderList[indexRender];
			
			return tileGraphic.isPassable;
		}
		
		protected var oldPosMapX:Number;
		protected var oldPosMapY:Number;
		protected var decalX:Number = 0;
		protected var decalY:Number = 0;
		protected var lockX:Boolean = false;
		protected var lockY:Boolean = false;
		
		protected function moveMap():void
		{
			oldPosMapX = quadTiles.x;
			oldPosMapY = quadTiles.y;
			
			if (lockX && lockY)
				return;
			
			if (!lockX)
			{
				quadTiles.x -= moveX;
				
				if (quadTiles.x < limitMinX)
				{
					quadTiles.x = limitMinX;
					moveX -= oldPosMapX - limitMinX;
					lockX = true;
				}
				else if (quadTiles.x > limitMaxX)
				{
					quadTiles.x = limitMaxX;
					moveX += limitMaxX - oldPosMapX;
					lockX = true;
				}
			}
			
			if (!lockY)
			{
				quadTiles.y -= moveY;
				
				if (quadTiles.y < limitMinY)
				{
					quadTiles.y = limitMinY;
					moveY -= oldPosMapY - limitMinY;
					lockY = true;
				}
				else if (quadTiles.y > limitMaxY)
				{
					quadTiles.y = limitMaxY;
					moveY += limitMaxY - oldPosMapY;
					lockY = true;
				}
			}
		}
		
		protected var oldPosHeroX:Number;
		protected var oldPosHeroY:Number;
		
		protected function moveHero():void
		{
			oldPosHeroX = hero.x;
			oldPosHeroY = hero.y;
			
			if (lockX)
			{
				hero.x += moveX;
				
				if (oldPosHeroX < centerPoint.x && hero.x >= centerPoint.x)
				{
					hero.x = centerPoint.x;
					moveX += oldPosHeroX - centerPoint.x;
					quadTiles.x -= moveX;
					lockX = false;
				}
				else if (oldPosHeroX > centerPoint.x && hero.x <= centerPoint.x)
				{
					hero.x = centerPoint.x;
					moveX -= centerPoint.x - oldPosHeroX;
					quadTiles.x += moveX;
					lockX = false;
				}
			}
			
			if (lockY)
			{
				hero.y += moveY;
				
				if (oldPosHeroY < centerPoint.y && hero.y >= centerPoint.y)
				{
					hero.y = centerPoint.y;
					moveY += oldPosHeroY - centerPoint.y;
					quadTiles.y -= moveY;
					lockY = false;
				}
				else if (oldPosHeroY > centerPoint.y && hero.y <= centerPoint.y)
				{
					hero.y = centerPoint.y;
					moveY -= centerPoint.y - oldPosHeroY;
					quadTiles.y += moveY;
					lockY = false;
				}
			}
		}
		
		protected function updateOriginTiles():void
		{
			decalX += quadTiles.x - oldPosMapX;
			decalY += quadTiles.y - oldPosMapY;
			
			if (decalX < -Scene.TILE_WIDTH)
			{
				decalX += Scene.TILE_WIDTH;
				originTileX++;
				edit = true;
			}
			else if (decalX > Scene.TILE_WIDTH)
			{
				decalX -= Scene.TILE_WIDTH;
				originTileX--;
				edit = true;
			}
			
			if (decalY < -Scene.TILE_HEIGHT)
			{
				decalY += Scene.TILE_HEIGHT;
				originTileY++;
				edit = true;
			}
			else if (decalY > Scene.TILE_HEIGHT)
			{
				decalY -= Scene.TILE_HEIGHT;
				originTileY--;
				edit = true;
			}
		}
		
		// TIMER //////////////////////////////////////////////////////////////////////////////////
		
		public function update(event:TimerEvent):void
		{
			quadTiles.reset();
			
			if (nbStep > 0)
			{
				move();
			}
			
			if (edit)
			{
				setRenderTileOrigin();
			}
			
			parseTile(updateTiles);
			
			edit = false;
		}
		
		public function start():void{}
		public function stop():void{}
		
		// KEY ////////////////////////////////////////////////////////////////////////////////////
		
		protected var keyLeft:Boolean;
		protected var keyRight:Boolean;
		protected var keyTop:Boolean;
		protected var keyBottom:Boolean;
		
		public function onKeyDown(event:KeyboardEvent):void
		{
			switch(event.keyCode)
			{
				case Keyboard.LEFT : 	keyLeft = true; 	break;
				case Keyboard.RIGHT : 	keyRight = true; 	break;
				case Keyboard.UP : 		keyTop = true; 		break;
				case Keyboard.DOWN : 	keyBottom = true; 	break;
			}
		}
		
		public function onKeyUp(event:KeyboardEvent):void
		{
			switch(event.keyCode)
			{
				case Keyboard.LEFT : 	keyLeft = false; 	break;
				case Keyboard.RIGHT : 	keyRight = false; 	break;
				case Keyboard.UP : 		keyTop = false; 	break;
				case Keyboard.DOWN : 	keyBottom = false; 	break;
			}
			
			//// TEST
			
			var increment:int = 50;
			
			switch(event.keyCode)
			{
				case Keyboard.LEFT :
					setMove(new Point(hero.x - increment, hero.y));
					break;
				case Keyboard.RIGHT :
					setMove(new Point(hero.x + increment, hero.y));
					break;
				case Keyboard.UP :
					setMove(new Point(hero.x, hero.y - increment));
					break;
				case Keyboard.DOWN :
					setMove(new Point(hero.x, hero.y + increment));
					break;
			}
			
			////
		}
		
		// MOUSE //////////////////////////////////////////////////////////////////////////////////
		
		protected var isMouseDown:Boolean = false;
		protected var globalDesti:Point = new Point();;
		
		public function onClick(event:TouchEvent):void
		{
			var touch:Touch = event.getTouch(this);
			
			if (!touch)
				return;
			
			//if (touch.phase == TouchPhase.BEGAN || touch.phase == TouchPhase.HOVER)
			if (touch.phase == TouchPhase.BEGAN)
				isMouseDown = true;
			
			if (touch.phase == TouchPhase.ENDED)
				isMouseDown = false;
			
			if (isMouseDown)
			{
				globalDesti.x = touch.globalX;
				globalDesti.y = touch.globalY;
				
				setMove(globalDesti);
			}
		}
	}
}